package quota

import (
	"fmt"

	"go-main/library/conf/env"
	"go-main/library/ecode"
	"go-main/library/log"
	bm "go-main/library/net/http/dam"
	quota2 "go-main/library/rate/limit/quota"
	"go-main/library/stat/metric"
)

var (
	_metricServerQuota = metric.NewCounterVec(&metric.CounterVecOpts{
		Namespace: "http_server",
		Subsystem: "",
		Name:      "quota_limit_total",
		Help:      "http server quota limit total.",
		Labels:    []string{"appid", "method", "extra"},
	})
)

type Config struct {
	// 是否禁用
	Disable bool
	// 只打日志不拦截
	Preflight bool
	// 环境 默认从环境变量中获取
	Env string
	// 机房地址 默认从环境变量中获取
	Zone string
	// 当前应用的appid 默认从环境变量中获取
	AppID string
	// 需要限制的方法
	Methods []*Method
}

type Method struct {
	// http 方法路径
	Path string
	// 规则 total/appkey/caller或自定义
	Rule string
	// 资源不存在则禁止访问 用于禁止未知的caller/appkey访问
	NotAllowStranger bool
	// 未注册资源请求统一使用公共额度 提供公共额度给未注册的caller/appkey使用
	StrangerCommonQuota bool
	// 自定义返回的ecode 默认-509
	Ecode int
}

type RateLimiter struct {
	conf    *Config
	methods map[string][]*method
}

type method struct {
	notAllowStranger    bool
	rule                Rule
	strangerCommonQuota bool
	ecode               int
}

type item struct {
	key              string
	fullKey          string
	strangerKey      string
	notAllowStranger bool
	ecode            int
}

// New return a ratelimit middleware.
func New(conf *Config, rs ...Rule) (s *RateLimiter) {
	conf = fixConfig(conf)
	rs = append(rs, &RuleTotal{}, &RuleCaller{}, &RuleAppkey{})
	ruleMap := map[string]Rule{}
	for _, r := range rs {
		ruleMap[r.Name()] = r
	}
	methods := make(map[string][]*method)
	for _, m := range conf.Methods {
		if r, ok := ruleMap[m.Rule]; !ok {
			panic(fmt.Sprintf("unknow rule: %v", m.Rule))
		} else {
			methods[m.Path] = append(methods[m.Path], &method{
				rule:                r,
				notAllowStranger:    m.NotAllowStranger,
				strangerCommonQuota: m.StrangerCommonQuota,
				ecode:               m.Ecode,
			})
		}
	}
	return &RateLimiter{
		conf:    conf,
		methods: methods,
	}
}

func fixConfig(conf *Config) (res *Config) {
	res = conf
	if res == nil {
		res = &Config{}
	}
	if res.AppID == "" {
		if env.AppID == "" {
			panic("bm quota: cannot get appid init failed")
		} else {
			res.AppID = env.AppID
		}
	}
	if res.Zone == "" {
		if env.Zone == "" {
			panic("bm quota: cannot get zone init failed")
		} else {
			res.Zone = env.Zone
		}
	}
	if res.Env == "" {
		if env.DeployEnv == "" {
			panic("bm quota: cannot get env init failed")
		} else {
			res.Env = env.DeployEnv
		}
	}
	for _, m := range conf.Methods {
		if m.Rule == "" {
			m.Rule = "total"
		}
		if m.Ecode == 0 {
			m.Ecode = ecode.LimitExceed.Code()
		}
	}
	return
}

func (l *RateLimiter) serveHTTP(c *bm.Context) {
	if l.conf.Disable {
		return
	}
	uri := c.RoutePath
	rs := l.methods[uri]
	var items []*item
	if len(rs) == 0 {
		return
	}
	for _, r := range rs {
		key := r.rule.Key(c)
		fullkey := fmt.Sprintf("%s.%s.%s|http|%s|%s", l.conf.Env, l.conf.Zone, l.conf.AppID, uri, key)
		var strangerKey string
		if r.strangerCommonQuota {
			strangerKey = fmt.Sprintf("%s.%s.%s|http|%s|common", l.conf.Env, l.conf.Zone, l.conf.AppID, uri)
		}
		items = append(items, &item{key: key, fullKey: fullkey, notAllowStranger: r.notAllowStranger, strangerKey: strangerKey, ecode: r.ecode})
	}
	for _, item := range items {
		limiter := quota2.NewAllower(&quota2.AllowerConfig{ID: item.fullKey, NotAllowStranger: item.notAllowStranger})
		allow := limiter.Allow()
		if limiter.UnknowResource() && item.strangerKey != "" {
			allow = quota2.NewAllower(&quota2.AllowerConfig{ID: item.strangerKey, NotAllowStranger: item.notAllowStranger}).Allow()
		}
		if !allow {
			_metricServerQuota.Inc(l.conf.AppID, uri, item.key)
			if l.conf.Preflight {
				log.Warn("quota: http not allow: %s", item.fullKey)
				return
			}
			c.JSON(nil, ecode.New(item.ecode))
			c.Abort()
			return
		}
	}
	return
}

// Handler is router allow handle.
func (l *RateLimiter) Handler() bm.HandlerFunc {
	return l.serveHTTP
}
